Leaflet. Дружим Image с Canvas / Хабр

您所在的位置:网站首页 leaflet map touch Leaflet. Дружим Image с Canvas / Хабр

Leaflet. Дружим Image с Canvas / Хабр

#Leaflet. Дружим Image с Canvas / Хабр| 来源: 网络整理| 查看: 265

Leaflet Map

Доброго времени суток, дорогие хабрахабровцы!

Leaflet — библиотека, позволяющая добавить интерактивные карты на Ваш сайт и легко их кастомизировать. Сегодня рассмотрим то, как можно разместить изображения на Canvas-слое карт, совместно с базовыми маркерами.

Задача Построить трек с отметкой различных статусов состояния. Статусы отмечаются маркерами. У каждого статуса есть свой приоритет.

Для оптимизации карты, рендеринг объектов должен происходить с использованием Canvas. Маркеры могут быть двух типов: точки и изображения. Если маркеры перекрывают друг друга — то сверху должен оказаться маркер более приоритетного статуса. Каждый маркер должен быть активным при наведении на него мышкой (например для вывода дополнительной информации). Подготовка Подключим библиотеку Leaflet.js и добавим базовую карту.

const map = L.map('map', { preferCanvas: true, }).setView([51.505, -0.09], 13); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map); Для наглядности будем использовать 3 состояния в порядке увеличение приоритета: базовый (зеленый маркер), сообщение (изображение) и ошибка (красный маркер).

Соответственно, красный маркер должен перекрывать изображение, а изображение — перекрывать зеленый маркер.

/* Базовый маркер */ L.circleMarker(L.latLng(51.52, -0.109), { radius: 10, fillColor: '#27ae60', fillOpacity: 1, color: '#fff', weight: 3, }).addTo(map); /* Маркер сообщения */ L.marker(L.latLng(51.52, -0.109), { icon: L.icon({ iconUrl: 'icon.png', // url картинки iconSize: [40, 40], // размер маркера iconAnchor: [20, 20], // выравнивание относительно центра }), }).addTo(map); /* Маркер ошибки */ L.circleMarker(L.latLng(51.52, -0.109), { radius: 8, fillColor: '#f44334', fillOpacity: 1, color: '#fff', weight: 3, }).addTo(map); Проблема Leaflet добавляет маркеры поочередно, поэтому каждый последующий должен перекрывать предыдущий. Но на деле это не так. L.marker добавляет изображение в качестве обыкновенного IMG, отдельно от слоя Canvas.

Его можно разместить либо перед, либо под Canvas. И как следствие, невозможно поместить L.marker между двух L.circleMarker.

Следовательно, нужен способ размещать изображения в том же Canvas, на который добавляются и стандартные маркеры.

Примечание: В сети есть несколько плагинов, позволяющих добавлять изображения на Canvas. Но они создают отдельный Canvas, или даже группу слоев! В итоге простое размещение маркеров по приоритету становится довольно затруднительным. А так же Canvas-слои перекрывают друг друга, и кликнуть мышкой на маркер нижестоящего слоя становится невозможным!

Решение Шаг 1. Создаем дочерний класс от L.CircleMarker, который будет получать объект 'img', загружать изображение и добавлять его в L.Canvas.

const CanvasMarker = L.CircleMarker.extend({ _updatePath() { if (!this.options.img.el) { //Создаем элемент IMG const img = document.createElement('img'); img.src = this.options.img.url; this.options.img.el = img; img.onload = () => { this.redraw(); //После загрузки запускаем перерисовку }; } else { this._renderer._updateImg(this); //Вызываем _updateImg } }, }); L.canvasMarker = function (...options) { return new CanvasMarker(...options); }; Шаг 2. Описываем метод _updateImg в L.Canvas. Он получает объект с изображением, который мы передаем на Шаге 1 и рисует его на Canvas.

L.Canvas.include({ _updateImg(layer) { //Метод добавления img на Canvas-слой const { img } = layer.options; const p = layer._point.round(); this._ctx.drawImage(img.el, p.x - img.size[0] / 2, p.y - img.size[1] / 2, img.size[0], img.size[1]); }, }); Шаг 3. Теперь вместо L.marker можно использовать L.canvasMarker. Обратите внимание, что параметр 'anchor' не используется, т.к. картинка выравнивается автоматически!

/* Базовый маркер */ L.circleMarker(L.latLng(51.52, -0.109), { radius: 10, fillColor: '#27ae60', fillOpacity: 1, color: '#fff', weight: 3, }).addTo(map); /* Маркер сообщения */ L.canvasMarker(L.latLng(51.52, -0.109), { img: { url: 'icon.png', size: [40, 40], }, }).addTo(map); /* Маркер ошибки */ L.circleMarker(L.latLng(51.52, -0.109), { radius: 8, fillColor: '#f44334', fillOpacity: 1, color: '#fff', weight: 3, }).addTo(map); В результате:

Все маркеры расположены на едином Canvas-слое. Маркеры перекрывают друг-друга в порядке их добавления на карту. При наведении на маркеры мышкой, они сохраняют активность. Задача решена!

Дополнительно Давайте «прокачаем» наш метод L.canvasMarker и добавим возможность автоматически разворачивать изображение в направлении движения по карте!

За основу возьмем координаты предыдущей точки. Для этого сначала доработаем метод _updateImg.

L.Canvas.include({ _updateImg(layer) { const { img } = layer.options; const p = layer._point.round(); if (img.rotate) { this._ctx.save(); this._ctx.translate(p.x, p.y); this._ctx.rotate(img.rotate * Math.PI / 180); this._ctx.drawImage(img.el, -img.size[0] / 2, -img.size[1] / 2, img.size[0], img.size[1]); this._ctx.restore(); } else { this._ctx.drawImage(img.el, p.x - img.size[0] / 2, p.y - img.size[1] / 2, img.size[0], img.size[1]); } }, }); Как видно из примера, для поворота у 'img' должно быть свойство 'rotate'. И мы уже можем задать его вручную при добавлении маркера:

L.canvasMarker(L.latLng(51.52, -0.109), { img: { url: 'icon.png', size: [40, 40], rotate: 15, //угол поворота изображения }, }).addTo(map); Но нам нужно вычислять угол поворота автоматически на основе предыдущей точки. Поэтому добавим вычисление угла на основе двух координат (angleCrds):

const angleCrds = (map, prevLatlng, latlng) => { if (!latlng || !prevLatlng) return 0; const pxStart = map.project(prevLatlng); const pxEnd = map.project(latlng); return Math.atan2(pxStart.y - pxEnd.y, pxStart.x - pxEnd.x) / Math.PI * 180 - 90; }; const CanvasMarker = L.CircleMarker.extend({ _updatePath() { if (!this.options.img.el) { /* Вызываем метод */ if (!this.options.img.rotate) this.options.img.rotate = 0; this.options.img.rotate += angleCrds(this._map, this.options.prevLatlng, this._latlng); const img = document.createElement('img'); img.src = this.options.img.url; this.options.img.el = img; img.onload = () => { this.redraw(); }; } else { this._renderer._updateImg(this); } }, }); L.canvasMarker(L.latLng(51.52, -0.109), { prevLatlng: L.latLng(51.528, -0.1), // Координаты предыдущей точки img: { url: 'icon.png', size: [40, 40], }, }).addTo(map); Заключение → Пример работы можно увидеть здесь → Весь описанный функционал я вынес в отдельный npm-плагин

Этот плагин легко подключить и использовать в своих проектах! Так же плагин поддерживает дополнительные настройки, не описанные в данной статье.

Спасибо за внимание!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3